﻿/*
ParallelPlatform Project - A web platform to distribute and share knowledge
Online Editor - An online tool to create and play animations
Copyright (C) 2011 Daniel Guerrero Martínez & Sergio Rodríguez Lumley

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package Classes{
	//import flash.display.Sprite;
	
	import flash.events.MouseEvent;
	
	import flash.text.TextField;
	import flash.text.TextFormat;
	
	import flash.utils.getQualifiedClassName;
	import flash.utils.getDefinitionByName;
	
	import flash.filters.BitmapFilterQuality;
	import flash.filters.BlurFilter;
	
	import flash.geom.Point;
	
	public class ShapeObject extends BasicShapeObject {
		
		public static const SINGLECLICK:int = 0;
		public static const DOUBLECLICK:int = 1;
		public static const MOUSEDOWN:	int = 2;
		
		/* The ID of a ShapeObject is based on two numbers
		with the next format: id.stage; where id is an increasing
		number and the stage is the ID of the stage.
		Please note any of both parts is assured to be of secuential
		increasing value.
		*/
		private var id:				String; // id.stage
		private var selectFunction:	Function;
		private var outOfBounds:	Boolean;
		private var outOfBoundsAdvisor:ShapeOutOfBounds;
		private var iniWidth:		Number;
		private var iniHeight:		Number;
		private var isEditable:		Boolean;
		private var canHaveText:	Boolean;
		private var text_txt:		TextField;
		private var removed:		Boolean;
		private var blurx:			Number;
		private var blury:			Number;
		private var hasShadow:		Boolean;
		private var shadowx:		Number;
		private var shadowy:		Number;
		private var colorArray:		Array; // Array(Point<color, numcoloredlines>)
		private var linesArray:		Array; // Array(Array<Point<x, y> >)
		
		/**
		  Gets the normal parameters to set the ID of the ShapeObject and
		  returns it as a FullID String
		*/
		static public function getAsID(num1:uint, num2:uint):String{
			return String(num1)+"."+String(num2);
		}
		
		/**
		Returns the second part of the str, excluding the point
		@param str String with full ID of the Shape
		*/
		public static function SceneOf(str:String):String{
			return str.substring(str.indexOf(".")+1, str.length);
		}
		
		/**
		 Constructor of ShapeObject
		 @param selectionFunction Function to trigger when selected, null by default
		*/
		public function ShapeObject(selectionFunction:Function = null){
			super();
			id 					= "0.0";
			this.buttonMode 	= true;
			this.useHandCursor 	= true;
			this.mouseChildren 	= false;
			this.doubleClickEnabled = true;
			this.setSelectionFunction(selectionFunction);
			outOfBounds 		= false;
			iniWidth 			= width;
			iniHeight 			= height;
			removed 			= false;
			blurx				= 0;
			blury				= 0;
			hasShadow			= false;
			shadowx				= 0;
			shadowy				= 0;
			linesArray			= null;
			colorArray			= null;
			isEditable			= ShapeObjectDatabase.isEditable(this);
			canHaveText			= ShapeObjectDatabase.hasText(this);
			if(canHaveText){
				text_txt 			= ShapeObjectDatabase.getTextField(this);
				this.addChild(text_txt);
			}
			
			//this.setText(this.getText());
			checkOutOfBounds();
		}
		
		/**
		Sets the function to trigger when object is clicked
		@param selectionFunction Function to trigger, has three arguments (MouseEvent, ShapeObject, int)
		*/
		public function setSelectionFunction(selectionFunction:Function):void{
			if(selectFunction != null){
				this.removeEventListener(MouseEvent.CLICK, thisSelected);
				this.removeEventListener(MouseEvent.MOUSE_DOWN, thisDown);
				this.removeEventListener(MouseEvent.DOUBLE_CLICK, thisEditing);
				this.buttonMode = false;
				this.useHandCursor = false;
				this.doubleClickEnabled = false;
			}
			selectFunction = selectionFunction;
			if(selectFunction != null){
				this.addEventListener(MouseEvent.CLICK, thisSelected);
				this.addEventListener(MouseEvent.MOUSE_DOWN, thisDown);
				this.addEventListener(MouseEvent.DOUBLE_CLICK, thisEditing);
				this.buttonMode = true;
				this.useHandCursor = true;
				this.doubleClickEnabled = true;
			}
		}
		
		/**
		Sets the ID of the ShapeObject
		@param num ID of the object
		*/
		public function setID(num:uint):void{
			id = String(num) + "." + getSecondPartID();
		}
		
		/**
		Gets the ID of the ShapeObject
		@ret ID of the object in int form
		*/
		public function getID():uint{
			return uint(getFirstPartID());
		}
		
		public function setStageID(num:uint):void{
			id = getFirstPartID() + "." + String(num);
		}
		
		public function getStageID():uint{
			return uint(getSecondPartID());
		}
		
		public function setFullID(fid:String):void{
			id = fid;
		}
		
		public function getFullID():String{
			return id;
		}
		
		public function hasText():Boolean{
			return canHaveText;
		}
		
		override public function getWidth():Number{
			return iniWidth;
		}
		
		override public function getHeight():Number{
			return iniHeight;
		}
		
		/**
		  Returns the blur of the shape in an array, as [blurx blury]
		*/
		public function getBlur():Point{
			return new Point(blurx, blury);
		}
		
		/**
		  Blurs the shape object, depending on the width and height of the blur
		  it will have more or less blur
		  @param xx Number Pixels to blur in X axis
		  @param yy Number Pixels to blur in Y axis
		*/
		public function setBlur(xx:Number, yy:Number){
			blurx = xx;
			blury = yy;
			if(blurx != 0 || blury != 0){
				var blur:BlurFilter = new BlurFilter(blurx, blury, BitmapFilterQuality.LOW);
				this.filters = [blur];
			}else{ // We add the filter
				if(this.filters.length > 0){ // Remove filter
					this.filters = [];
				}
			}
		}
		
		/**
		  Sets a shadow in the object
		  @param xx Number Distance in pixels to project in X axis
		  @param yy Number Distance in pixels to project in Y axis
		  @param activate Boolean specifing if we want to activate or desactivate
		  		shadows, (true by default)
		*/
		public function setShadow(xx:Number, yy:Number, activate:Boolean = true){
			hasShadow 	= activate;
			shadowx		= xx;
			shadowy		= yy;
			// TODO Set the shadows in the filter
		}
		
		/**
		  Hides the text of the object, does not check if it has text or not
		*/
		public function hideText():void{
			text_txt.alpha = 0;
		}
		
		/**
		  Hides the text of the object, does not check if it has text or not
		*/
		public function showText():void{
			text_txt.alpha = 1;
		}
		
		public function getText():String{
			return text_txt.text;
		}
		
		/**
		  Sets the text of the ShapeObject, adjusts the size and position
		  @param txt String with the new text
		*/
		public function setText(txt:String):void{
			text_txt.text = txt;
		}
		
		public function getTextFormat():TextFormat{
			return text_txt.getTextFormat();
		}
		
		public function getTextHeight():Number{
			return text_txt.height;
		}
		
		public function getTextY():Number{
			return text_txt.y;
		}
		
		public function getColors():Array{
			return colorArray;
		}
		
		public function getLines():Array{
			return linesArray;
		}
		
		public function setColors(colors:Array):void{
			colorArray = colors;
		}
		
		/**
		  Sets the drawing lines of this object, this also
		  modifies the initial width and height of the object,
		  which is calculated as the maximum width and height
		  between the initial one and the new one defined by
		  the lines
		  @param lines Array<Array<Point>> lines to paint
		*/
		public function setLines(lines:Array):void{
			linesArray = lines;
			if(lines != null){
				
				var leftTop:	Point = new Point(Number.MAX_VALUE, Number.MAX_VALUE);
				var rightBottom:Point = new Point(Number.MIN_VALUE, Number.MIN_VALUE);
				var point:		Point;
				
				// Get the right values of the corners
				for each(var line:Array in linesArray){
					for each(point in line){
						if(point.x > rightBottom.x) rightBottom.x = point.x;
						if(point.y > rightBottom.y) rightBottom.y = point.y;
						
						if(point.x < leftTop.x) leftTop.x = point.x;
						if(point.y < leftTop.y) leftTop.y = point.y;
					}
				}
				
				iniWidth = Math.max(iniWidth, rightBottom.x - leftTop.x);
				iniHeight = Math.max(iniHeight, rightBottom.y - leftTop.y);
			}
		}
		
		/**
		  Adds the specified values to X and Y coordinates of the shape,
		  checks if Shape goes out of bounds or not
		  @param xx Number to add to X
		  @param yy Number to add to Y
		*/
		public function moveRelative(xx:Number, yy:Number):void{
			x += xx;
			y += yy;
			checkOutOfBounds();
		}
		
		/**
		  Sets the shape to the specified values by X and Y,
		  checks if Shape goes out of bounds or not
		  @param xx Number to add to X
		  @param yy Number to add to Y
		*/
		public function moveTo(xx:Number, yy:Number):void{
			x = xx;
			y = yy;
			checkOutOfBounds();
		}
		
		/**
		  Duplicates this shapeObject
		  @param withEvents Boolean that indicates if also copy eventListeners (true) or not (false), by default NOT copy them
		*/
		public function duplicate(withEvents:Boolean = false):ShapeObject{
			var reference:String = getQualifiedClassName(this);
			var ClassReference:Class = getDefinitionByName(reference) as Class;
			var dup:ShapeObject = new ClassReference();//Object(this).constructor;
			
			if(canHaveText)dup.setText(this.getText());
			dup.setID(this.getID());
			dup.setStageID(this.getStageID());
			dup.x 			= this.x;
			dup.y 			= this.y;
			dup.iniWidth 	= this.iniWidth;
			dup.iniHeight 	= this.iniHeight;
			dup.scaleX 		= this.scaleX;
			dup.scaleY 		= this.scaleY;
			dup.rotation 	= this.rotation;
			dup.setShadow(this.shadowx, this.shadowy, this.hasShadow);
			dup.setBlur(this.blurx, this.blury);
			dup.isEditable 	= this.isEditable;
			dup.colorArray 	= this.colorArray;
			dup.linesArray 	= this.linesArray;
			dup.repaint();
			dup.name 		= this.name;
			dup.checkOutOfBounds();
			
			if(withEvents){
				dup.setSelectionFunction(selectFunction);
			}else{
				dup.buttonMode 			= false;
				dup.useHandCursor 		= false;
				dup.mouseChildren 		= false;
				dup.doubleClickEnabled 	= false;
			}
			
			return dup;
		}
		
		/**
		  Returns wether this shapeObject is deleted
		*/
		public function isRemoved():Boolean{
			return removed;
		}
		
		/**
		  Marks this shapeObject as deleted
		*/
		public function remove():void{
			if(! removed){
				removed = true;
				if(canHaveText){
					this.repaint();
					graphics.lineStyle(5,0xFF3333);
					graphics.moveTo(5 - iniWidth/2, 5 - iniHeight/2);
					graphics.curveTo(0, -iniHeight/4, iniWidth/2 -5, iniHeight/2 -5);
					graphics.moveTo(5 - iniWidth/2, iniHeight/2 -5);
					graphics.curveTo(0, -iniHeight/4, iniWidth/2-5, 5 - iniHeight/2);
				}
				alpha = 0.2;
			}
		}
		
		/**
		  Unmarks this shapeObject as deleted
		*/
		public function unremove():void{
			if(removed){
				removed = false;
				graphics.clear();
				this.repaint();
				alpha = 1;
			}
		}
		
		/**
		  Repaints this shape if it is editable
		*/
		public function repaint():void{
			if(isEditable && linesArray != null){
				this.graphics.clear();
				var i:uint = 0;
				var j:uint = 0;
				var color:uint = 0;
				var nextColor:uint = 0;
				var point:Point;
				var first:Boolean = true;
				
				for(i=0; i<linesArray.length; ++i){ // For each line
					if(i == nextColor){
						nextColor = i+colorArray[color].y;
						graphics.lineStyle(5, colorArray[color].x);
						++color;
					}
					first = true;
					for(j=0; j<linesArray[i].length; ++j){
						point = linesArray[i][j] as Point;
						if(first){
							first = false;
							graphics.moveTo(point.x, point.y);
						}else{
							graphics.lineTo(point.x, point.y);
						}
					}
				}
			}
		}
		
		/**
		  Clears the graphics of this shape if it is editable
		  By clearing the graphics we mean that we repaint
		  the shape without the painted lines over it
		*/
		public function clearPaint():void{
			if(isEditable){
				this.graphics.clear();
			}
		}
		
		/**
		Prepares this object to being removed, this is done by
		eliminating all external references and removing eventListeners
		*/
		public function clean():void{
			this.setSelectionFunction(null);
		}
		
		/************** PRIVATE FUNCTIONS *****************/
		
		/**
		Function to trigger when object is clicked
		*/
		private function thisSelected(evt:MouseEvent){
			selectFunction(evt, this, SINGLECLICK);
		}
		
		/**
		Function to trigger when object is double clicked
		*/
		private function thisEditing(evt:MouseEvent){
			if(evt.shiftKey)
				selectFunction(evt, this, SINGLECLICK);
			else
				selectFunction(evt, this, DOUBLECLICK);
		}
		
		/**
		Function to trigger when object is click-holded
		*/
		private function thisDown(evt:MouseEvent){
			selectFunction(evt, this, MOUSEDOWN);
		}
		
		/**
		Returns the first part of the id, excluding the point
		*/
		private function getFirstPartID():String{
			return id.substring(0, id.indexOf("."));
		}
		
		/**
		Returns the second part of the id, excluding the point
		*/
		private function getSecondPartID():String{
			return id.substring(id.indexOf(".")+1, id.length);
		}
		
		private function checkOutOfBounds():void{
			if((x-iniWidth/2 > 700 || x+iniWidth/2 < 0 || y-iniHeight/2 > 500 || y+iniHeight/2 < 0) && outOfBounds==false){
				// If the shape goes out for the first time...
				outOfBounds = true;
				// If it's the first time it has ever came out, we create an advisor
				if(outOfBoundsAdvisor == null){
					outOfBoundsAdvisor = new ShapeOutOfBounds();
				}
				this.addChild(outOfBoundsAdvisor);
			}else if(! (x-iniWidth/2 > 700 || x+iniWidth/2 < 0 || y-iniHeight/2 > 500 || y+iniHeight/2 < 0) && outOfBounds == true){
				// If the comes back to stage and it was outside...
				outOfBounds = false;
				this.removeChild(outOfBoundsAdvisor);
			}
			
			if(outOfBounds){
				if(x+iniWidth/2 > 700) outOfBoundsAdvisor.x = 700-x;
				else if(x-iniWidth/2 < 0) outOfBoundsAdvisor.x = -x;
				else outOfBoundsAdvisor.x = 0;
				
				if(y+iniHeight/2 > 500) outOfBoundsAdvisor.y = 500-y;
				else if(y-iniHeight/2 < 0) outOfBoundsAdvisor.y = -y;
				else outOfBoundsAdvisor.y = 0;
			}
		}
		
	}
}